<!-- List.vue -->
<template>
  <div
    :style="{ height: containerHeight }"
    @scroll="handleScroll"
    class="container"
    ref="container"
  >
    <!-- 数据列表 -->
    <div
      :style="{ top: listTop }"
      class="list"
    >
      <!-- 列表项 -->
      <div
        :key="item.id"
        :style="{ height: size + 'px' }"
        v-for="item in showData"
      >
        {{ item.content }}
      </div>

      <!-- 用于撑开高度的元素 -->
      <div
        :style="{ height: barHeight }"
        class="bar"
      />
    </div>
  </div>
</template>

<script lang="ts">
  export default {
    name: 'VirtualList',
    props: {
      // 要渲染的数据
      items: {
        type: Array,
        required: true
      },
      // 每条数据渲染的节点的高度
      size: {
        type: Number,
        required: true
      },
      // 每次渲染的 DOM 节点个数
      shownumber: {
        type: Number,
        required: true
      }
    },
    data() {
      return {
        start: 0, // 要展示的数据的起始下标
        end: this.shownumber // 要展示的数据的结束下标
      }
    },
    computed: {
      // 最终筛选出的要展示的数据
      showData() {
        return this.items.slice(this.start, this.end)
      },
      // 容器的高度
      containerHeight() {
        return this.size * this.shownumber + 'px'
      },
      // 撑开容器内容高度的元素的高度
      barHeight() {
        return this.size * this.items.length + 'px'
      },
      // 列表向上滚动时要动态改变 top 值
      listTop() {
        return this.start * this.size + 'px'
      }
    },
    methods: {
      // 容器的滚动事件
      handleScroll() {
        // 获取容器顶部滚动的尺寸
        const scrollTop = this.$refs.container.scrollTop

        // 计算卷去的数据条数，用计算的结果作为获取数据的起始和结束下标
        // 起始的下标就是卷去的数据条数，向下取整
        this.start = Math.floor(scrollTop / this.size)
        // 结束的下标就是起始的下标加上要展示的数据条数
        this.end = this.start + this.shownumber
      }
    }
  }
</script>

<style scoped>
  .container {
    position: relative;
    overflow-y: scroll;
    background-color: rgb(150, 195, 238);
    font-size: 20px;
    font-weight: bold;
    line-height: 60px;
    text-align: center;
  }

  .list {
    position: absolute;
    top: 0;
    width: 100%;
  }
</style>
